using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Fated.Graphics;

namespace Fated {
    public static class MenuRenderer {
        // Animating is a mask that includes Open/Close/Closing
        private enum MenuStates { 
            Ready = 0, 
            Animating = 1 << 1, 
            OpenAnimating = Animating | (1 << 2), 
            CloseAnimating = Animating | (1 << 3), 
            Closing = Animating | (1 << 4), 
            Cancelled = Closing | (1 << 5),
            Finished = (1 << 6)
        }

        private const double SLIDEDURATION = .2;
        private static int BORDERTHICKNESS = 4;
        private const float MENUWIDTH = 1f;// / 4f;
        private const float MENUHEIGHT = 1f;// / 2f;
        private const string MENUFONT = "fonts\\defaultFont";
        private const string BORDERFILE = "graphics\\border";

        private static Border border;

        private static int screenWidth;
        private static int screenHeight;
        private static int menuWidth;
        private static int menuHeight;

        private static Tree<string> choices;
        private static Tree<string> currentLevel;

        private static List<int> choiceIndices = new List<int>();
        private static int currentChoice = 0;

        private static MenuStates currentState = MenuStates.Ready;
        private static Vector2 topOffset;
        private static long tweenKey;

        private static long stick = -1;

        private static SpriteBatch spriteBatch;
        private static GraphicsDevice graphics;
        private static RenderTarget2D textureOut;
        private static Texture2D blankSquare;
        private static SpriteFont menuFont;

        public static void initialize(GraphicsDevice gD, SpriteBatch sB, ContentManager cM, int sHeight, int sWidth) {
            screenHeight = sHeight;
            screenWidth = sWidth;
            graphics = gD;
            spriteBatch = sB;

            menuWidth = (int)(screenWidth * MENUWIDTH);
            menuHeight = (int)(screenHeight * MENUHEIGHT);

            blankSquare = new Texture2D(gD, 1, 1);
            blankSquare.SetData<Color>(new Color[] { Color.White });

            textureOut = new RenderTarget2D(gD, menuWidth, menuHeight);
            menuFont = cM.Load<SpriteFont>(MENUFONT);
            border = new Border(cM.Load<Texture2D>(BORDERFILE));
            BORDERTHICKNESS = (int)MathHelper.Max(border.TopBorderThickness, border.SideBorderThickness);

            stick = TimingMngr.registerControl();

            border.Depth = 1f;
        }

        public static void showMenu(Tree<string> menuTree){
            choiceIndices.Clear();
            choices = menuTree;
            currentLevel = choices;

            currentChoice = 0;
            startAnimating(MenuStates.OpenAnimating);
            render();
        }

        private static string buildString(Tree<string> data) {
            if (data.Count() == 0) {
                return " ";
            }
            StringBuilder sB = new StringBuilder();
            for (int i = 0; i < data.Count(); ++i) {
                string s = data[i].Data;
                sB.AppendLine(s);
            }
            return sB.ToString(0, sB.Length - 1);
        }

        public static void update(InputState padState, double dTime) {
            if ((currentState & MenuStates.Animating) == MenuStates.Animating) {
                if (TimingMngr.tweenFinished(tweenKey)) {
                    TimingMngr.finishTween(tweenKey);
                    tweenKey = -1;
                    topOffset = Vector2.Zero;

                    if ((currentState & MenuStates.Closing) == MenuStates.Closing) {
                        if (currentState != MenuStates.Cancelled) {
                            choiceIndices.Add(currentChoice);
                        }
                        currentState = MenuStates.Finished;
                    }
                } else {
                    topOffset = TimingMngr.updateTween(tweenKey, dTime);
                }
                render();
            }

            // We stop taking inputs when finished
            if (Finished || !Started) return;

            if (TimingMngr.tickAvailable(padState.PrimaryStick, stick, dTime)) {
                // Subtract because down negative on stick, but positive on list
                currentChoice -= (int)Math.Round(padState.PrimaryStick.Y);
                if (currentChoice >= currentLevel.Count()) {
                    currentChoice = 0;
                } else if (currentChoice < 0) {
                    currentChoice = currentLevel.Count() - 1;
                }
                render();
            }

            if (padState.UniqueSelectButtonPress) {
                if (currentLevel[currentChoice].Count() == 0) {
                    startAnimating(MenuStates.Closing);
                } else {
                    choiceIndices.Add(currentChoice);
                    currentLevel = currentLevel[currentChoice];
                    startAnimating(MenuStates.OpenAnimating);
                    currentChoice = 0;
                    render();
                }
            } else if (padState.UniqueCancelButtonPress) {
                // Back down one if possible
                if (choiceIndices.Count() > 0) {
                    currentChoice = choiceIndices[choiceIndices.Count() - 1];
                    choiceIndices.RemoveAt(choiceIndices.Count() - 1);
                    currentLevel = choices.tracePath(choiceIndices.ToArray());
                    startAnimating(MenuStates.CloseAnimating);
                    // Otherwise cancel the menu
                } else {
                    startAnimating(MenuStates.Cancelled);
                }
                render();
            }
        }

        private static void startAnimating(MenuStates nextState) {
            string text = buildString(currentLevel);
            Vector2 size = menuFont.MeasureString(text) + new Vector2(2 * BORDERTHICKNESS, 2 * BORDERTHICKNESS);
            if (nextState == MenuStates.OpenAnimating) {
                tweenKey = TimingMngr.createTween(new Vector2(-size.X, 0), Vector2.Zero, SLIDEDURATION);
            } else if (nextState == MenuStates.CloseAnimating) {
                tweenKey = TimingMngr.createTween(Vector2.Zero, new Vector2(-size.X, 0), SLIDEDURATION);
            } else if ((nextState & MenuStates.Closing) == MenuStates.Closing) {
                tweenKey = TimingMngr.createTween(Vector2.Zero, new Vector2(-ActualSize.X, 0), SLIDEDURATION);
            }
            currentState = nextState;
            topOffset = TimingMngr.updateTween(tweenKey, 0);
        }

        private static void render() {
            graphics.SetRenderTarget(textureOut);
            graphics.Clear(Color.Transparent);
            spriteBatch.Begin();

            // This will cause a transparent menu when finished
            if (!Finished && Started) {
                Tree<string> path = choices;
                Vector2 offset = Vector2.Zero;

                // This depth doesn't matter too much because it's being rendered to texture
                float renderDepth = .9f;
                // Render decided levels
                for (int i = 0; i < choiceIndices.Count(); i++) {
                    if (currentState == MenuStates.Closing) {
                        offset = renderMenu(path, offset + topOffset, choiceIndices[i], renderDepth);
                    } else {
                        offset = renderMenu(path, offset, choiceIndices[i], renderDepth);
                    }
                    renderDepth -= .009f;
                }
                // Render level in decision
                renderMenu(currentLevel, offset + topOffset, currentChoice, .1f);
                ActualSize += offset;
            }

            spriteBatch.End();
            graphics.SetRenderTarget(null);
        }
        private static Vector2 renderMenu(Tree<string> data, Vector2 offset, int selected, float renderDepth) {
            string text = buildString(data);
            Vector2 size = menuFont.MeasureString(text);
            float lineHeight = menuFont.MeasureString("Tli").Y - 3;
            Vector2 sizeWBorder = size + new Vector2(2 * BORDERTHICKNESS, 2 * BORDERTHICKNESS);
            // top left corner
            Vector2 position = new Vector2(BORDERTHICKNESS, BORDERTHICKNESS) + offset;

            border.Position = offset;
            border.Size = sizeWBorder;
            border.Depth = renderDepth + .001f;
            border.drawBorder(spriteBatch);

            spriteBatch.DrawString(menuFont, text, position, Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, renderDepth);

            // Draw selection
            spriteBatch.Draw(blankSquare,
                new Rectangle((int)position.X, (int)(lineHeight * selected + position.Y), (int)size.X, (int)lineHeight),
                null, Color.FromNonPremultiplied(0, 0, 255, 128), 0f, Vector2.Zero, SpriteEffects.None, renderDepth - .001f);

            ActualSize = sizeWBorder;

            return offset + new Vector2(size.X / 4, lineHeight * (selected + 1f));
        }

        public static string getResult() {
            int[] temp;
            currentState = MenuStates.Ready;
            return getResult(out temp);
        }
        public static string getResult(out int[] path) {
            path = choiceIndices.ToArray();
            if (choiceIndices.Count() == 0) return null;
            return currentLevel[path[path.Length - 1]].Data;
        }

        public static RenderTarget2D getTexture() {
            return textureOut;
        }

        public static int Height {
            get {
                return menuHeight;
            }
        }
        public static int Width {
            get {
                return menuWidth;
            }
        }

        public static bool Started { 
            get { return currentState > 0; } 
        }
        public static bool Finished {
            get { return currentState == MenuStates.Finished; }
        }

        public static Vector2 ActualSize { get; internal set; }
    }
}
